package ua.yandex.shad.tempseries;

import java.util.Arrays;

public class TemperatureSeriesAnalysis {
    private static final double MIN_ALLOWED_TEMP = -273;
    private static final int MIN_FORECAST_THRESHOLD = 10;

    private final double[] temperatureSeries;
    private final int seriesLength;

    public TemperatureSeriesAnalysis(double[] temperatureSeries) {
        this.temperatureSeries = Arrays.copyOfRange(
                temperatureSeries, 0, temperatureSeries.length);
        seriesLength = temperatureSeries.length;

        checkAllowedTempValues();
    }

    private void checkAllowedTempValues() {
        double[] tempsLessThenAllowed = findTempsLessThen(MIN_ALLOWED_TEMP);

        if (tempsLessThenAllowed.length > 0) {
            String messageTemplate = "%f is less then min allowed %f";
            String message = String.format(messageTemplate,
                    tempsLessThenAllowed[0], MIN_ALLOWED_TEMP);
            throw new IllegalTemperatureValueException(message);
        }
    }

    public double calcAverageTemp() {
        double sum = 0;

        for (double d : temperatureSeries) {
            sum += d;
        }

        return sum / temperatureSeries.length;
    }

    /**
     * Calculate Sample Standard Deviation.
     *
     * @return calculated Sample Standard Deviation,
     * or <code>0</code> is series is empty.
     * @see <a href="http://en.wikipedia.org/wiki/Standard_deviation">
     * Standard deviation</a>.
     */
    public double calcTempDeviation() {
        double average = calcAverageTemp();
        double squaredSum = 0;

        for (double temperature : temperatureSeries) {
            double difference = temperature - average;
            squaredSum += difference * difference;
        }

        return Math.sqrt(squaredSum / Math.max(seriesLength - 1, 1));
    }

    public double findTempClosestToZero() {
        return findTempClosestToValue(0);
    }

    public double findTempClosestToValue(double tempValue) {
        double closestTempToValue = 0;

        if (!isEmpty()) {
            closestTempToValue = temperatureSeries[0];
        }

        for (double temperature : temperatureSeries) {
            double currentClosestTempDelta =
                    calcTempDelta(tempValue, closestTempToValue);
            double currentTempDelta = calcTempDelta(tempValue, temperature);

            if (currentTempDelta < currentClosestTempDelta) {
                closestTempToValue = temperature;
            } else if (currentTempDelta == currentClosestTempDelta) {
                closestTempToValue = Math.max(closestTempToValue, temperature);
            }
        }

        return closestTempToValue;
    }

    private double calcTempDelta(double firstTemp, double secondTemp) {
        double max = Math.max(firstTemp, secondTemp);
        double min = Math.min(secondTemp, firstTemp);

        return max - min;
    }

    public double[] findTempsLessThen(double tempValue) {
        int lessTempsCount = 0;

        for (double temperature : temperatureSeries) {
            if (temperature < tempValue) {
                lessTempsCount++;
            }
        }

        double[] lessTemps = new double[lessTempsCount];
        int lessTempsIndex = 0;

        for (double temperature : temperatureSeries) {
            if (temperature < tempValue) {
                lessTemps[lessTempsIndex++] = temperature;
            }
        }

        return lessTemps;
    }

    public double[] findTempsGreaterThen(double tempValue) {
        int greaterOrEqualTempsCount = 0;

        for (double temperature : temperatureSeries) {
            if (temperature >= tempValue) {
                greaterOrEqualTempsCount++;
            }
        }

        double[] greaterOrEqualTemps = new double[greaterOrEqualTempsCount];
        int greaterOrEqualTempsIndex = 0;

        for (double temperature : temperatureSeries) {
            if (temperature >= tempValue) {
                greaterOrEqualTemps[greaterOrEqualTempsIndex++] = temperature;
            }
        }

        return greaterOrEqualTemps;
    }

    private boolean isEmpty() {
        return temperatureSeries.length == 0;
    }

    /**
     * Forecast next 3 temperature values with SSA.
     *
     * @return next 3 temperature values with SSA
     * @throws NotEnoughValuesToForecastException if temperature series has less
     *                                            then 10 values;
     * @see <a href="https://en.wikipedia.org/wiki/Singular_spectrum_analysis
     * #Main_algorithm_of_SSA">Main algorithm of SSA</a>
     */
    public double[] forecastTemp() {
        checkLengthForForecastPrediction();

        return new double[]{};
    }

    private void checkLengthForForecastPrediction() {
        if (seriesLength < MIN_FORECAST_THRESHOLD) {
            String template = "Expected series of size >= %d but was of %d";
            String message = String.format(
                    template, MIN_FORECAST_THRESHOLD, seriesLength);
            throw new NotEnoughValuesToForecastException(message);
        }
    }

}
